/*
一、变量(variable)
    变量声明
        声明、赋值分解
        单一var
    命名规则：
        1.变量名必须以英文字母 _ $ 开头
        2.变量名可以包括引文字母 _ $ 数字
        3.不可以用系统的关键字、保留字作为变量名



例如：
    申请一个变量并复制
    var a = 10;

    同时申请多个变量，也可以复制
    var a,
        b,
        c,
        d,
        e = 10;




原始值：是存在stack(栈)  栈内存之间的复制是copy
    Number Boolean String null undefined
        
引用值：大致是放在heap(堆)   堆内存之间的复制是copy地址
    array Object function data RegExp


引用值和引用值比较的是 地址



js语句基本规则：
    语句后面要用分号结束;  特殊的不用加分号：function test(){}    for(){}  if(){} 这些都不用加
    js语法错误会引发代码终止，但不会影响其他js代码块
        错误分成两种：
            1.低级错误(语法错误)
            2.逻辑错误(逻辑错误，情有可原)
    书写格式要规范，= + / - 两边都应该有空格




运算操作符：
    +号：
        1.数学运算、字符串连接
        2.任何数据类型加字符串都等于字符串
    
    - * / % = ()
    优先级 = 最弱, ()优先级最高
    ++   --      +=       -=     /=   *=      %= 




    逻辑运算符:
    &&   与   找false
    ||   或   找true
    !    非

*/

const { stringify } = require("querystring");



// 判断数据的类型，typeof可以返回6中数据类型 number string boolean  object undefined funcation
// typeof(a),变量没有定义就使用在typeof里面不报错，只有这一种情况
var a = 10;
data_type =  typeof(a)    



// 显示类型转换
Number()   //转换成数字类型的 undefinde转换成NaN
parseInt()   //转换成整形,从数字位看是看，砍掉非数字为后面的所有
var demo = '10'
console.log(parseInt(demo,16))   //以目标机制为基底转换成10进制，把16进制的10转换成10进制，16也可以变成2进制，8进制




parseFloat()   //转换成小数,
toFixed()  //方法可把 Number 四舍五入为指定小数位数的数字。
var num = 123123.345789
console.log(typeof(num.toFixed(3)))

toString()     //转换成字符串,谁要转换成字符串就  谁.toString() ,有两个特殊点，undefined和null不能使用toString方法
// tostring也可以做进制转换例如：
var demo = 10;
console.log(demo.toString(8))  //以十进制为基底，转换成8进制，就是把10转换成8进制,括号里面可以填2 8 16 

String()       //转换成字符串
Boolean()     //转换成true或者false



// 把这个10101010转换成16进制,()
var two = 10101010;
var shiliu = parseInt(two,2) //先把这个转换成二进制转换成十进制
console.log(shiliu.toString(16))   





// 截取字符串的方法
string.substr(start,stop);  //这个方法会把截取的结果返回
substring(start,stop)
string.includes('abc')  //这个字符串包含abc吗，如果包含则返回true，否则返回false





// 隐式类型转换
isNaN(123);  //先把isNaN方法就是先把要转换的数先用Number()方法转换，然后转换出来的结果在和NaN比对
// ++和-- +和-(正负)  使用这些都会变成数字类型的,使用的隐式类型转换是Number()
// +            加号的隐式类型转换是string()
// - * / %      使用的是Number() 隐式类型转换
// && ||        调用Boolean()
// /* <> <= >= */   有数字的就会隐式的调用Number()       
// ==  !=        隐式的调用Number()



// 不发生类型转换
// ===     绝对等于：视觉上等于就等于
// !===    绝对不等于：视觉上不等于就不等于







// javascript中几种为false的值
// 如果JavaScript预期某个位置应该是布尔值，会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false，其他值都视为true。
// undefined
// null
//  false
//  0
//  NaN
// ""或''（空字符串）
// 需要特别注意的是，空数组（[]）和空对象（{}）对应的布尔值，都是true。








// 做条件判断
var score = parseInt(window.prompt('input'));    
// 90 - 100 ali
//80 - 90 didi
// 70 - 80 58
// 60 - 70 蘑菇街
// 60 一下 肯定不是我教的

if (score > 90 && score <= 100){
    document.write("ali")
}else if(score > 80 && score <= 90){
    document.write("didi")
}else if(score > 70 && score <= 80){
    document.write("58")
}else if(score > 60 && score <= 70){
    document.write("蘑菇街")
}else if(score < 60 && score >= 0){
    document.write("肯定不是我教的");
}else{
    document.write("输入有误");
}




// for循环的执行顺序
// 1.var i = 0;
// 2.if(i < 10){
//     document.write(i)
// }
// 3.i ++


//    1            2     3
for(var i = 0; i < 10; i++){
    document.write(i)
    
}


//continue  跳过当前循环
// break    停止当前循环


// while循环
var i = 0;
while(i < 10){
    document.write(i)
    i ++
}



// switch case    switch是条件    case是判断
var n = 2;
switch(n){
    case 1:
        console.log("a");
        break;
    case 2:
        console.log("b");
        break;
    case 3:
        console.log("C");
        break;
}







// 函数


// 1.函数声明
function print(){
    console.log("hello world")
}



// 3.匿名函数表达式 ----函数表达式
var demo = function () {
    console.log("hello world")
}


// 形参、实参
// 当a大于b的时候做a-b  否则a+b
function demo(a, b) {
    if(a > b){
        return a - b
    }else{
        return a + b
    }
}
var value = demo(10, 8)
console.log(value)



// JavaScript不定参：可以实参多行参少，也可以形参多实参少
function demo(a) {
    //arguments --- [1, 2, 3] 实参列表
    console.log(arguments);   
    for(var i = 0; i < arguments.length; i++) {
        console.log(arguments[i])
    }
}
demo(1, 2, 3)





//小练习，判断是形参多还是实参多      函数名.lenght可以看实参的长度
function print(a, b, c, d) {
    if(print.length > arguments.length) {
        console.log("形参多了")
    }else if(print.lenght < arguments.lenght){
        console.log("实参多了")
    }else {
        console.log("相等")
    }
}
print(1, 2, 3, 4, 5, 6)


// 不定参的好处，比如我要求任意位数的和
function sum() {
    var result = 0;
    for(var i = 0; i < arguments.length; i++) {
        result += arguments[i]
    }
    return result
}
var number = sum(1, 2, 3, 4, 5, 6, 8, 9, 10)
console.log(number)





// 实参对应形参 a和arguments[0]它两相互映射
function sum(a, b, c) {
    a = 2;
    console.log(arguments[0] = 4)
    console.log(a)
}
sum(1, 2, 3, 4, 5)


// 实参不对应形参，b和arguments[1]它两不映射
function sum(a, b) {
    b = 2;
    console.log(arguments[1])
}
sum(1)




// return的作用函数的结束条件 + 返回值
function sum() {
    var result = 0;
    for(var i = 0; i < arguments.length; i++) {
        result += arguments[i]
    }
    return result
}
var number = sum(1, 2, 3, 4, 5, 6, 8, 9, 10)
console.log(number)




// 递归最重要的两点      1.找规律    2.找出口
// 使用递归的方法计算n的阶乘   公式：n * (n - 1)
function demo(n) {
    if(n == 1 || n == 0){
        return 1;
    }
    return n * demo(n - 1);
}
var  num = demo(10)
console.log(num)




// 使用常识的方法计算斐波那契数列
function fb(n) {
    var a = 0;
    var b = 1;
    var c = 0;
    for(i = 0; i < n-1; i++){
        c = a + b;
        a = b;
        b = c;
    }
    console.log(c)
}
fb(10)

// 使用递归的方法计算斐波那契数列
function fb(n) {
    if(n == 1 || n ==0) {
        return 1;
    }
    return fb(n - 1) + fb(n - 2); 
}
var fb_value = fb(10)
console.log(fb_value)










// 立即执行函数
// 定义：此类函数没有声明，在执行过后释放。适合做初始化工作。



// 预编译前奏
// 1.imply global暗示全局变量：即任何变量，如果变量未经声明就赋值，此变量就为全局对象所有。
// a = 234;
// var a = b = 123;

// 2.一切声明的全局变量，全是windows的属性。
// var a = 123; ===> window.a = 123

// 3.window就是全局的域



// 预编译:预编译发生在函数执行的前一刻
// 预编译四部曲(函数的)：
// 1.创建AO对象(Activation Object(活跃对象)，就是作用域，也叫执行期上下文)
// 2.找形参和变量声明，将变量和形参作为AO属性名，值为undefined
// 3.将实参值和形参统一
// 4.在函数体里面找函数声明，值赋予函数体

// 预编译三部曲(全局的)
// 1、创建GO对象：执行期上下文（Global Object）
// 2、寻找变量声明，并且当做属性放在GO对象里，值为undefined
// 3、寻找函数声明，值赋予函数体




// 函数的预编译练习

// 分析
// AO{
//     a:functioni a(){}
//     b:undefind
//     d:function d() {}
// }

function fn(a) {
    console.log(a);   //function () {}
    var a = 123;
    console.log(a);    //123
    function a() {}
    console.log(a);    //123
    var b = function () {}  
    console.log(b)   //function () {}

    function d() {}
}
fn(1)   




function test(a, b) {
    console.log(a);   //1
    c = 0;
    var c;
    a = 3;
    b = 2;
    console.log(b);    //2
    function b() {}
    function d() {}
    console.log(b)      //2
}
test(1)




function test(a, b) {
    console.log(a)    //function a() {}
    console.log(b)    //undefind 
    var b = 234;   
    console.log(b)   //234
    a = 123;
    console.log(a)   //123
    function a() {}
    var a;
    b = 234;    
    var b = function () {}
    console.log(a)   //123
    console.log(b)   //function () {}
}
test(1)








// Object.assign()对象覆盖
// 下面方法的意思就是使用options对象中的属性覆盖defaults对象中的属性
// assign会影响原对象，所以不用接收也不用赋值
Object.assign(defaults, options)








// 作用域精解
/* 
[[scope]]:每个javascript函数都是一个对象，对象中有些属性我们可以访问，但有些不可以，
这些属性仅供javascript引擎存取，[[scope]]就是其中一个
[[scope]]指的就是我们所说的作用域，其中存储了运行期上下文的集合。

作用域链：[[scope]]中所存储的执行器上下文对象的集合，这个集合呈链式连接，
         我们把这种链式连接叫做作用域。




执行器上下文：当函数执行时，会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境，
            函数每次执行时对应的执行期上下文都是独一无二的，所以到此调用一个函数会创建多个执行期上下文，当函数
            执行完毕，他所产生的执行期上下文被销毁。

查找变量：从作用域链的顶端依次向下查找。(补充：在哪个函数里面查找变量，就从哪个函数的作用域链的顶端依次向下查找)



*/



// 作用域链练习
function a(){
    function b() {
        function c() {

        }
        c();
    }
    b();
}
a();



// 上题产生的作用域链
// a defined a.[[scope]] --> 0 : GO
// a doing a.[[scope]]   --> 0 : aAO
//                       --> 1 : GO


// b defined b.[[scope]] --> 0: aAO
//                       --> 1 : GO

// b doing   b.[[scope]] --> 0 : bAO
//                       --> 1 : aAO
//                       --> 2 : GO


// c defined c.[[scope]] --> 0 : bAO
//                       --> 1 : aAO
//                       --> 2 : GO


// c doing c.[[scope]]   --> 0 : cAO
//                       --> 1 : bAO
//                       --> 2 : aAO
//                       --> 3 : GO





// 闭包：当内部函数被保存到外部时，将会生成闭包。闭包会导致原有作用域不释放，造成内存泄漏。
// 闭包的作用


// 模块化开发
// 1.函数累加器
function count() {
    var count = 0;
    function demo() {
        count ++
        console.log(count)
    }
    return demo
}
var test = count();
test();  //1
test();  //2
test();  //3
test();  //4
test();  //5


// 2.可以做缓存(存储结构)
function test() {
    var num = 100;
    function a() {
        num ++;
        console.log(num);
    }

    function b() {
        num --;
        console.log(num);
    }
    return [a,b]
}


var my_arr = test();
my_arr[0]();
my_arr[1]();



function eater() {
    var food = ""
    var obj = {
        eat : function () {
            console.log("i am eating " + food);
            food = "";
        },
        push : function (myFood) {
            food = myFood;
        }
    }
    return obj;
}
var myobj = eater();
myobj.push("banana")
myobj.eat()











// 立即执行函数的定义：此类函数没有声明，在一次执行过后即释放。适合做初始化工作。
// 针对初始化功能的函数

// 立即执行函数的写法
(function (){}())    //W3C建议使用第一种
(function () {})()



var num = (function (a, b, c){
    c = a + b;
    return c;
}(100,200))
console.log(num);



(function () {
    console.log("下层基础决定上层建筑")
})();



// 只有表达式才能被执行符号执行





// 闭包的触发
function test() {
    var arr = [];
    for (var i = 0; i < 10; i++) {
        arr[i] = function () {
            document.write(i + " ")
        }
    }
    return arr;
}

var myarr = test();
for (var j = 0; j < 10; j++) {
    myarr[j]();    //最终打印出来10个10
}


// 使用立即执行函数解决上述闭包问题
function test() {
    var arr = [];
    for(var i = 0; i < 10; i ++) {

        // 在这个位置套一个立即执行函数，把i当成实参传进取
        (function (j) {
            arr[j] = function () {
                document.write(j + " ")
            }
        }(i));
    }
    return arr
}


var myarr = test();
for(var j = 0; j < 10; j ++) {
    myarr[j]();
}





// 利用闭包实现的小功能
function test() {
    var food = "apple";
    var obj = {
        eat_food : function () {
            if(food != "") {
                console.log("eatfood " + food)
                food = "";
            }else {
                console.log("什么都没吃");
            }
        },
        push_food : function (my_food) {
            food = my_food;
        }
    }
    return obj;
}
var result = test();
result.eat_food();
result.eat_food();
result.push_food("banana")
result.eat_food();


// this.innerText 获取标签里面的内容


// 计算一个字符串的字节长度
function byte_lenght(target) {
    byte_size = 0;
    for(var i = 0; i < target.length; i ++){
        if(target[i].charCodeAt() <= 255){
            byte_size += 1;
        }else if(target[i].charCodeAt() > 255){
            byte_size += 2;
        }
    }
    console.log(byte_size)
}
byte_lenght("计算字符串长度")



// 对象
// 在对象中，变量没有定义就访问，不会报错返回一个undefined
var obj = {
    name : "zhangsan",
    age : 18,
    sex : "男"
}
// 增
obj.height = "180cm"
// 删
delete obj.sex
// 改
obj.age = 20
// 查
obj.name


// 对象的创建方法
// 第一种方法
var obj = {};  //对象字面量

// 第二种方法
var obj = new Object();  //使用系统自带的构造函数

// 自定义
function myobj() {   
}
var myobj1 = new myobj();

// 构造函数  命名规则：大驼峰式命名规则(单词的首字母大写)

// 自定义构造函数,有自带的属性，也有选配的
function Factory(color) {
    this.color = color,
    this.name = "zhangsan",
    this.sex = "男",
    this.age = 18,
    this.money = 10000,
    this.run = function () {
        this.money --
    }
}
var Factory_one = new Factory("red");
var Factory_two = new Factory("blue");

// 构造函数的内部原理
// 1.在函数体最前面隐式的加上this = {};
// 2.执行this.xxx = xxx;
// 3.隐式的返回this



function Person(name, height) {
    this.name = name;
    this.height = height;
    this.say = function () {
        console.log(this.say);
    }
}
var person_one = new Person("zhangsan", "180cm");
// 包装类
// var num = new Number(123);
// var str = new String('abcd');
// var bol = new Boolean('true');



// 原型
// 1.定义：原型是function对象的一个属性，它定义了构造函数制造出的对象的公共祖先。
      //  通过该构造函数产生的对象，可以继承该原型的属性和方法。原型也是对象。
// 2.利用原型特点和概念，可以提取共有属性。
// 3.对象如何看原型---> 隐式属性__proto__
// 4.对象如何查看对象的构造函数---->constructor


Person.prototype.name = "hehe";
function Person() {
}
var person = new Person();
var person1 = new Person();



Person.prototype.name = "deng";
Person.prototype.say = function () {
    console.log("hehe")
}
function Person() {
}
var person = new Person();
var person1 = new Person();

person.say();
person1.say();


Person.prototype.surname = "Deng"
Person.prototype.sex = "nan"
function Person(name, age, height, weight) {
    this.name = name;
    this.age = age;
    this.height = height;
    this.weight = weight;
}

var person_one = new Person();
var person_two = new Person();


// 原型的增删改查

Person.prototype.lastname = "deng"
Person.prototype.sex = "nan"
function Person(height, weight) {
    this.height = height;
    this.weight = weight;
}
var person = new Person('180cm', '70kg');
Person.prototype.age = 18;  //增
delete Person.prototype.sex  //删
Person.prototype.lastname = "zhang"  //改
Person.prototype.lastname   //查



// 原型的另外一种写法
Person.prototype = {
    lastname : 'zhang',
    sex : 'nan',
}
function Person(height, weight) {
    this.height = height;
    this.weight = weight;
}

var person = new Person();
console.log(person.lastname)





// 原型链
// 1.如何构成原型链
// 2.原型链上属性的增删改查
// 3.绝大多数对象的最终都会继承自Object.prototype
// 4.Object.create(原型);
var obj = {
    lastname : 'zhang',
    name : 'san',
    age : 18,
    sex : 'female',
}
var obj1 = Object.create(obj);  //如果在里面添Null就会没有prototype
console.log(obj1)





// 重写toString方法，在一个原有的prototype方法中，重新写一个名字一模一样的
// 覆盖原有的方法，这个叫重写
var num = 123;
Number.prototype.toString = function() {
    console.log("重写成功")
}
num.toString();



// 给Number添加一个length方法
var num = 123;
Number.prototype.length = function(target) {
    var arr = [];
    var str_num = String(target);
    for(var i = 0; i < str_num.length; i ++){
        arr[i] = Number(str_num[i]);
    }
    return arr.length;
}
var arr = num.length(1234567890);
console.log(arr)



// javascript可正常计算的范围，小数点前16位，后16位







// call / apply 
    // 作用：改变this指向
    // 区别，后面传的参数的形式不同

// call需要把实参按照形参的个数穿进去
// apple 需要传一个arguments

function Person(name, age) {
    this.name = name; 
    this.age = age;
}
var person = new Person('deng', 100);
var obj = {

}
Person.call(obj, 'li', 18);
console.log(obj)


// 借用别人的函数实现自己的功能
// 例子1用call
function Person(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}

function Student(name, age, sex, tel, grade) {
    Person.call(this, name, age, sex);  //重点
    this.tel = tel;
    this.grade = grade;
}
var student = new Student('sunny', 20, 'female', 1522, 2020);




// 例子二用apply
function Wheel(Wheel_size, style) {
    this.style = style;
    this.Wheel_size = Wheel_size;
}

function Sit(c, sit_color) {
    this.c = c;
    this.sit_color = sit_color;
}
function Model(height, weight, len) {
    this.height = height;
    this.weight = weight;
    this.len = len;
}

function Car(Wheel_size, style, c, sit_color, height, weight, len) {
    Wheel.apply(this, [Wheel_size, style]);
    Sit.apply(this, [c, sit_color]);
    Model.apply(this, [height, weight, len]);
}
var car  = new Car(100, '骚气', 1, 'red', '4000cm', '200cm', 10);




// 继承发展史
// 1.传统形式---->原型链
    // 过多的继承了没用的属性

// 2.借用构造函数
    // 不能继承借用构造函数的原型
    // 每次结构都要走一个函数

// 3.共享原型
    // 不能随便改动自己的原型
    Father.prototype.lastname = 'deng';
    function Father() {}
    function Son() {}
    Son.prototype = Father.prototype;
    var son = new Son();
    var father = new Father();

// 4.圣杯模式
    Father.prototype.lastname = 'deng';
    function Father() {}
    function F() {};
    F.prototype = Father.prototype;
    Son.prototype = new F();
    function Son() {}
    Son.prototype.name = 'zhangsan';
    var son = new Son();
    var father = new Father();

    // 圣杯模式(完美版)

    Father.prototype.lastname = 'deng';
    function Father() {}
    // 写法1
    function inherit(Target, Origin) {
        function F() {}
        F.prototype = Origin.prototype;
        Target.prototype = new F();
        Target.prototype.constructor = Target;
        Target.prototype.uber = Origin.prototype;
    }
    inherit(Son, Father)
    function Son() {}
    Son.prototype.name = 'zhangsan';
    var son = new Son();
    var father = new Father();
    
    
    
    // 写法二(建议写第二种)
    var inherit = (function () {
        var F = function () {};
        return function (Target, Origin) {
            F.prototype = Origin.prototype;
            Target.prototype = new F();
            Target.prototype.constructor = Target;
            Target.prototype.uber = Origin.prototype;
        }
    }())





// 向下取整
Math.floor(123.9);  //返回123

// 向上取整
Math.ceil(123.1); //返回124

// 产生一个随机数
Math.random()






// 命名空间：管理变量，防止污染全局，适用于模块化开发
// 曾经用来解决命名空间的问题(对象来解决)
var org = {
    one : {
        jc : {
            name : 'jicheng',
            age : 18,
            sex : 'male',
            lastname : 'deng',
        },
        dcm : {

        },
    },
    two : {
        zhangsan : {

        },
        lisi : {

        }

    }
}

var jicheng = org.one.jc
console.log(jicheng.name)





// 使用闭包的方法来解决命名空间的问题
var name = 'test';    //这个name和里面的name互不冲突
var init = (function () {
    var name = "abc";
    function className() {
        console.log(name);
    }
    return function () {
        className();
    }
}());
init()
// webpack(是用来解决命名空间的问题)现在的办法







// 实现一个可以连续调用的方法
var obj = {
    name : function () {
        console.log('zhangsan');
        return this
    },
    age : function () {
        console.log(18);
        return this
    },
    sex : function () {
        console.log('female')
        return this
    },
    lastname : function () {
        console.log('deng');
        return this
    },
}
obj.name().age().sex().lastname();









// 对象的另外一种调用方式
var obj = {
    name : 'zhang',
    age : 18,
}
console.log(obj['name'])




// 对象的遍历(使用for in 循环)
var obj = {
    name : 'san',
    age : 18,
    sex : 'male',
    height : '180cm'
}
// 对象有多少个属性就循环多少圈
for(var i in obj) {
    console.log(i + " : " + obj[i]);
}



// 只遍历自己的，不便利原型上的方法
var obj = {
    name : 'san',
    age : 18,
    sex : 'male',
    height : '180cm',
    __proto__ : {
        lastname : 'deng'
    }
}

// 对象有多少个属性就循环多少圈
for(var i in obj) {
    if(obj.hasOwnProperty(i)){ //该方法可以用来判断这个属性名是不是自己的
        console.log(i + " : " + obj[i]);
    }   
}




// A对象 是不是 B构造函数构造出来的(官方解释)
// 看A对象原型链上  有没有 B的原型(上面那句话不完全，这句话就完全了)
// A instanceof B
function Person() {}
var person = new Person();
console.log(person instanceof Person)






// 判断是数组还是对象方法1
var arr = {}
if(arr instanceof Array){
    console.log('是数组')
}else{
    console.log('是对象')
}



// 判断是数组还是对象方法2(建议使用这种方法)
console.log(Object.prototype.toString.call([]))  //返回[object Array]
console.log(Object.prototype.toString.call({}))  //返回[object Object]
console.log(Object.prototype.toString.call(123))  //返回[object Number]



// 判断是数组还是对象方法3
var arr = []
if(arr.constructor == Array){
    console.log("是数组")
}else{
    console.log('是对象')
}


// 逗号操作符
// 例如我在控制台上 1, 2 他会看一眼前面的然后返回后面的
var num = (1, 2);
console.log(num) //输出2






// this知识点
// 1.函数预编译过程this --> windows
function test() {
    console.log(this);
}
test();
// 2.全局作用域里的 this -->windows
console.log(this)
// 3.call/apply 可以改变函数运行时的this指向
// 4.obj.func();  func()里面的this指向 obj


// this面试题
var name = '222';
var a = {
    name : '111',
    say : function () {
        console.info(this.name);
    }
}
var fun = a.say;
fun();  //222
a.say();  //111
var b = {
    name : "333",
    say : function (fun) {
        fun();
    }
}
b.say(a.say);  //222
b.say = a.say;  
b.say();  //333





// 注意：在es5标准模式中，这两个方法无法使用
// arguments
// arguments.callee : 函数引用
function test() {
    console.log(arguments.callee)  //打印函数引用
}
test();

// func.caller ：谁调用了
function test() {
    demo();
}
function demo() {
    console.log(demo.caller)  //打印射雕用了demo方法
}
test()






// 浅层克隆 
var obj = {
    lastname : 'zhang',
    name : 'san',
    age : 18,
    sex : 'female'
}
var obj1 = {}
function clone(origin, target) {
    var target = target || {}
    for(var i in origin){
        target[i] = origin[i]
    }
    return target
}
clone(obj, obj1);
console.log(obj1)








// 三目运算符
// 条件判断 ? 是 : 否  并且会返回值
// 例如: 1大于0吗 ? 是返回1 : 不是返回0
console.log(1 > 0 ? 1 : 0)  //返回1
// 复杂一点的写法
console.log(1 > 0 ? ("10" > 9 ? 1 : 0) : 2)  //返回1





// 深度克隆
person = {
    lastname : 'zhang',
    name : 'san',
    age : 18,
    sex : 'male',
    wife : 'xiaozhang',
    height : '180cm',
    weight : '70kg',
    card : ['visa', 'master'],
    obj : {
        test : 'test',
        son : {
            name : 'abc'
        }
    }
}
obj = {}
// 遍历对象for(var i in obj)
// 1.判断是不是原始值    typeof()
// 2.判断是数组还是对象   
// 3.建立相应的数组或对象
// 4.递归

function deepClone(origin, target) {
    var target = target || {},
        toStr = Object.prototype.toString,
        arrStr = '[object Array]';
    for(var prop in origin) {
        if(origin.hasOwnProperty(prop)){
            if(origin[prop] !== 'null' && typeof(origin[prop]) == 'object'){
                target[prop] = (toStr.call(origin[prop]) == arrStr) ? [] : {}
                deepClone(origin[prop], target[prop]);
            }else{
                target[prop] = origin[prop];
            }
        }
    }
    return target
}
deepClone(person, obj)







// 数组
// 数组的定义

var arr = [];             //字面量的方法
var arr = new Array(lenght, content);    //new的方法

// 改变原数组   push pop shift unshift sort reverse  splice
// 不改变原数组  concat, join---> split, toString, slice
// 数组的读和写
arr[num] //不可以溢出读
arr[num] = xxx;   //可以溢出写



arr.indexOf('zhangsan')  //获取数组中zhangsan的索引
// 数组的常用方法(改变原数组的)
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// 在数组的最后一位添加
arr.push('添加一个内容')
// push的内部原理
Array.prototype.push = function (target) {
    this[this.length] = target
    obj.length++;
}

arr.unshift("在数组的第0为添加东西")

// 实现一个和push一样的方法
Array.prototype.mypush = function() {
    for(var i = 0; i < arguments.length; i++){
        this[this.length] = arguments[i];
    }
    return this.length
}



var value = arr.pop()  // 剪切数组的最后一位
var value = arr.shift()   //剪切数组的第0位


arr.reverse();   //把数组倒叙
// arr.splice(从第几位开始, 截取多少位, 在接口处添加新的数据)
// 例如：(在第二位的地方插入一个张三)
arr.splice(2,0,'张三')




// 1.必须写两个形参
// 2.看返回值
    //   1.当返回值为负数时，那么前面的数放在前面
    //   2.为整数，那么后面的数在前，
    //   3.为0，不动

// 数组的排序(升序) 写法1
arr.sort(function (a, b) {
    return a > b ? 1 : -1 
});
// 数组的排序(升序) 写法2
arr.sort(function (a, b) {
    return a - b
});





// 数组的排序(降序) 写法1
arr.sort(function (a, b) {
    return a > b ? -1 : 1
});

// 数组的排序(降序) 写法2
arr.sort(function (a, b) {
    return b - a
});



// 给一个有序的数组，实现乱序
arr.sort(function (a, b) {
    return Math.random() - 0.5;
})
console.log(arr)








var zhang = {
    name : 'zhangsan',
    age : 18,
    sex : 'male',
}
var deng = {
    name : 'lisi',
    age : 20,
    sex : 'female',
}
var yuan = {
    name : 'yewei',
    age : 19,
    sex : 'male',
}
// 给这个数组按照年龄排序
var arr = [zhang, deng, yuan]
arr.sort(function(a, b) {
    return a.age - b.age
})



// 按照字符串长度排序
var arr = ['fdafda', 'fdjffdsafdsaf', 'fjkda', 'fa', 'a', 'fjlsfa']
arr.sort(function (a, b) {
    return a.length - b.length
})



// 数组的拼接
var arr = [1, 2, 3, 4, 5]
var arr1 = [6, 7, 8, 9, 10]
var result = arr.concat(arr1)   //concat把两个数字拼接成一个新数组
console.log(result)



// 把数组的每一位变成字符串
var arr = [1, 2, 3, 4, 5]
var result = arr.toString()   //把数组的每一个变成字符串


// 数组的截取
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var result = arr.slice(0, 3)// slice(该该位开始解决，截取到该位)  [1, 2, 3]
var result1 = arr.slice(1)  //从第一位开始截取截取到最后
var result2 = arr.slice()   //整个截取


// join把数组的每一个拿出来，用传的参数连接起来，返回一个连接好的字符串
var arr = [1, 2, 3, 4, 5]
var result = arr.join('-')  //把数组的每一给连接起来，用传的参数连接，最后返回一个字符串




// split把字符串按照什么差分(和join是互逆的)
var str = "1-2-3-4-5"
var result = str.split('-')  //把str这个字符串按照-拆分成每一给放到数组里



// 练习：例如我要把三个公司的名字连接起来
var str1 = 'alibb';
var str2 = 'tengxun';
var str3 = 'wangyi';
var arr = [str1, str2, str3]
var result = arr.join("")
console.log(result)










// 类数组(长的像数组，也可以当数组用，但是它就不是数组)
// 1.可以利用属性名模拟数组的特性
// 2.可以动态的正常length属性
// 3.如果强行让类数组调用push方法，则会根据length属性值的位置进行属性的扩充




// 类数组
// 类数组的特征：
    // 属性要为(数字)索引, 必须有length属性，最好加上push
var obj = {
    '0' : 'a',
    '1' : 'b',
    '2' : 'c',
    '3' : 'd',
    'length' : 3,
    'push' : Array.prototype.push,
    'splice' : Array.prototype.splice,   //加上splice就和数组和像了
}
obj.push('e')
console.log(obj)







// 练习题
// 封装type
function type(target) {
    // 1.分两类  原始值  引用值
    // 2.区分引用至
    var template = {
        '[object Array]' : 'array',
        '[object Object]' : 'object',
        '[object Number]' : 'number - object',
        '[object Boolean]' : 'boolean - object',
        '[object String]' : 'string - object'
    }
    if(target === null) {
        return null
    }
    if(typeof(target) == 'object'){
        // 数组 对象 包装类
        var str = Object.prototype.toString.call(target);
        return template[str]

    }else{
        return typeof(target)
    }
}







// 数组去重
// 要求在原型链上编程
// 自己写的
Array.prototype.unique = function () {
    var obj = {
        'length' : 0,
    }
    for(var i = 0; i < this.length; i ++) {
        var result = this[i]
        obj[result] = ''
    }
    this.splice(0)
    for(var i in obj) {
        if(i != 'length'){
            this.push(i)
        }
    }  
}
var arr = [1, 1, 2, 2, 3, 3, 'zhang', 'zhang']
arr.unique()


// 成哥写的
Array.prototype.unique = function () {
    var temp = {},
        arr = [],
        len = this.length;
    for(var i = 0; i < len; i ++) {
        if(!temp[this[i]]){
            temp[this[i]] = 'abc';
            arr.push(this[i]);
        }
    }
    return arr;
}
arr.unique()



// 修改成哥写的方法
var arr = [1, 2, 2, 3, 3, 4, 4, 4, 4]
Array.prototype.quchong = function () {
    var obj = {};
    var array = [];
    var len = this.length;
    for(var i = 0; i < len; i++) {
        if(!obj[this[i]]) {
            obj[this[i]] = this[i]
            array.push(this[i])
        }
    }
    this.splice(0)
    for(var i = 0; i < array.length; i ++) {
        this.push(array[i])
    }
    return this
}






// 封装一个方法找任意一个字符串中第一个只出现一次的字符
var str = 'qa*z1ws.xedcr1f1vtgbyhnujmikolqazwsxedcrfvtgbyhnujmikol';
function xuan(str) {
    var arr = str.split("")
    var result = []
    var count = 0;
    for(var j = 0; j < arr.length; j++) {
        for(var i = 0; i < str.length; i ++) {
            if(arr[j] == str[i]) {
                count ++;
            }
        }
        if(count == 1) {
            result.push(arr[j])
        }
        count = 0;
    }
    return console.log(result[0])
}
xuan(str)









// try ... catch 
// try{}catch(e){} finally{}
// Error.name的六种植对应的信息：
// 下面的东西需要背下来
// 1.EvalError : eval()的使用与定义不一致
// 2.RangeError：数值越界
// 3.ReferenceError：非法或不能识别的引用数值
// 4.SyntaxError：发生语法解析错误
// 5.TypeError：操作数据类型错误
// 6.URlError：url处理函数使用不当


// 在try里面发生错误，不会执行错误后的try里面的代码
// 当try里面发生错误将立刻跳到catch里面执行catch里面的代码,catch里面有个e
// e里面有两个东西一个是e.name(错误名称)   e.message(错误信息)
// 如果try里面没有错误将不会只想catch里面的代码
try{
    console.log('a');
    console.log(b)         
    console.log('c')
}catch(e) {   //error  error.massage error.name = error
    console.log(e.name)     //错误的名称
    console.log(e.message)  //错误信息
}





// with的用法,但是with不好，效率非常慢，所以在es5严格模式下无法使用
var obj = {
    dp1 : {
        jc : {
            name : 'jicheng',
            age : 18,
            lastnaem : 'ji'
        },
        yyw : {
            name : 'yyw',
            age : 18,
            lastname : 'zhangsan',
        }
    }
}
with(obj.dp1.jc){
    console.log(name)
}

with(obj.dp1.yyw){
    console.log(name)
}



// eval可以把字符串当做代码来使用(不建议使用)
eval("console.log('123')")



// 字符串分割(split)
var string = 'yuanyewei';
string.split('ye');  //以ye这个字符分割，分割之后是一个数组，数组的第0位是yuan,第一位是wei
string.split('ye')[0];  //获取数组的第一位

// 字符串替换
var string = '0.1243214321';
string.replace('.', '')  //把这个点替换为空



// es5严格模式
// 'use strict'     //(严格模式(就是启用es5.0的方法))必须写在页面的最顶端
// 不在兼容es3的一些不标准规则语法。使用全新的es5规范。
// 两种用法：
    // 全局严格模式
    // 局部函数内严格模式(推荐)
// 就是一行字符串，不会对不兼容严格模式的浏览器产生影响
// 不支持with, arguments.callee, func, caller, 变量赋值前必须声明,局部this
// 必须被赋值(Person.call(null/undefined)赋值什么就是什么)拒绝重复属性和参数



// 基于es3.0的 + es5.0的新增方法 使用的
// es3.0 和es5.0产生的冲突的部分
// es5.0严格模式 那么es3.0和es5.0产生冲突的部分就是用es5.0否则会用es3.0

// 'use strict'
// function test() {
//     console.log(arguments.callee);
// }
// test();



// 二维数组
// 二维数组
var arr = [
    [1, 2, 3],
    [2, 3, 4],
    [3, 4, 5],
]
console.log(arr[1][1]);


// json是一种数据传输的格式(以对象为样板，本质上就是对象
// 但是用途有区别，对象就是用本地用的,json是用来传输的)
JSON.parse();  //把json变成对象的形式
JSON.stringify(); //把对象编程json的形式

// 练习
var obj = {
    name : 'zhangsan',
    age : 18,
    sex : 'male',
}
var json = JSON.stringify(obj);   //把obj这个对象变成json格式的
console.log(json);
obj = JSON.parse(json);      //把json格式的字符串变成对象
console.log(obj);





// 异步加载js
// js加载的缺点：加载工具方法没必要阻塞文档，
// 过多js加载会影响页面效率，一旦网速不好，
// 那么整个网站将js加载而不进行后渲染等工作。

// 有些工具方法需要按需加载，用到在加载，不用不加载




// JavaScript异步加载的三种方案
// 1.defer异步加载，但要等到dom文档全部解析完才会被执行，
// 只有IE能用，也可以将代码写到内部。
// 例如：
    // <script src="./js.js" defer="defer"></script>


// 2.async异步加载，加载完就执行，
// async只能加载外部脚本，不能把js写在script标签里面。
// 1.2执行时也不阻塞页面
// 例如：
    // <script src="./js.js" async="async"></script>


// 3.创建script，插入到DOM中，加载完毕后callBack。(最常用)
// 例如：在一个主js里面创建一个script标签插入到页面里面去
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = './demo.js';
document.head.appendChild(script);

// 如果我要在主js里面调用demo.js里面的方法，但是有怕demo.js还没有下载完毕，
// 这个时候就可以用load这个方法,在load里面调用demo.js的方法,就不会存在没有下载完成就调用的情况
window.onload = function() {
    //这里代表加载完毕
}
// IE里面没有load方法,IE的方法如下
script.onreadystatechange = function() {
    if(script.readyState == "complte" || script.readyState == "loaded") {
        //这里代表加载完毕
    }
}






// js加载时间线
// 1.创建Document对象，开始解析web页面，
// 解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中，
// 阶段document.readyState = 'loading'。

// 2.遇到link外部css，创建线程加载，并继续解析文档

// 3.遇到script外部js，并且没有设置async、defer，浏览器加载，
// 等待js加载完成并执行该脚本，然后继续解析文档

// 4.遇到script外部js，并且有async、defer，浏览器创建线程加载，
// 并继续解析文档，对于async属性的脚本，脚本加载完成后立即执行。(异步禁止使用document.write())

// 5.遇到img等，先正常解析dom结构，然后浏览器异步加载css，并继续解析文档。

// 6.当文档解析完成，document.readyState = 'interactive'。

// 7.文档解析完成后,所有设置defer的脚本会按照顺序执行。(注意于async的不同，
// 但同样禁止使用document.write())

// 8.document对象触发DOMContentLoaded事件，这也标志着程序执行从脚本执行阶段，转化为事件驱动阶段。

// 9.当所有async的脚本加载完成并执行后、img等加载完成后，document.readyState = 'complete',
// window对象触发

// 10.从此，以异步响应方式处理用户输入、网络事件等。





document.onreadystatechange = function() {
    console.log(document.readyState) //输出事件状态
}



// 让script写到head里面，不适用window.onload方法，也可以处理下面的代码
document.addEventListener('DOMComtentLoaded', function() {
    // 这里面相当于window.onload = function() {}, 但是比它好，比他快
}, false)